/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.Bsp
{
	using Graphics;

	public sealed class Surface : GeometrySurface
	{
		internal Surface( SurfaceBuffer buffers )
			: base( buffers )
		{
		}

		private Material material;
		public Material Material
		{
			get { return material; }
			internal set { material = value; }
		}

		private int firstVertex;
		public int FirstVertex
		{
			get { return firstVertex; }
			internal set { firstVertex = value; }
		}

		private int numVerts;
		public int VertexCount
		{
			get { return numVerts; }
			internal set { numVerts = value; }
		}

		private int firstIndex;
		public int FirstIndex
		{
			get { return firstIndex; }
			internal set { firstIndex = value; }
		}

		private int numPrims;
		public int PrimitiveCount
		{
			get { return numPrims; }
			internal set { numPrims = value; }
		}

		internal uint addCounter;
	}

	internal sealed class SurfaceBuffer : GeometrySource
	{
		private int vertStride;
		private VertexDeclaration decl;
		private VertexBuffer vb;
		
		private IndexBuffer ib;
		private ushort[] sib;
		private uint[] iib;

		internal SurfaceBuffer( VertexDeclaration decl, VertexBuffer vb, IndexBuffer ib )
		{
			this.decl = decl;
			this.vb = vb;
			this.ib = ib;

			vertStride = decl.GetVertexStrideSize( 0 );
		}

		protected internal override void Begin( ShadingContext context, IPrerenderHandler prerenderHandler )
		{
			GraphicsDevice device = context.GraphicsDevice;

			device.VertexDeclaration = decl;
			device.Vertices[0].SetSource( vb, 0, vertStride );

			if( prerenderHandler != null)
				device.Indices = ((PrerenderHandler)prerenderHandler).IndexBuffer;
			else
				device.Indices = ib;
		}

		protected internal override void DrawSurfaces( DrawRecord[] surfaces, int index, int count, ShadingContext context, IPrerenderHandler prerenderHandler )
		{
			GraphicsDevice device = context.GraphicsDevice;
			PerformanceCounters pc = context.PerformanceCounters;

			int lastSurf = index + count;

			PrerenderHandler ph = prerenderHandler as PrerenderHandler;
			if( ph != null )
			{
				int primCount = 0;
				int vertCount = 0;
				for( int i = index; i < lastSurf; i++ )
				{
					Surface surf = surfaces[i].surface as Surface;
					primCount += surf.PrimitiveCount;
					vertCount += surf.VertexCount;
				}

				device.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0,
					(surfaces[index].surface as Surface).FirstVertex, vertCount,
					surfaces[index].PrerenderParam, primCount );

				pc.UpdateGeometryCounters( vertCount, primCount, 1 );
			}
			else
			{
				for( int e, s = index; s < lastSurf; s = e )
				{
					Surface ss = surfaces[s].surface as Surface;

					int primCount = ss.PrimitiveCount;
					int vertCount = ss.VertexCount;

					for( e = s + 1; e < lastSurf; e++ )
					{
						Surface se = surfaces[e].surface as Surface;
						if( ss.FirstVertex + vertCount != se.FirstVertex )
							break;

						vertCount += se.VertexCount;
						primCount += se.PrimitiveCount;
					}

					device.DrawIndexedPrimitives( PrimitiveType.TriangleList,
						0, ss.FirstVertex, vertCount, ss.FirstIndex, primCount );
					pc.UpdateGeometryCounters( vertCount, primCount, 1 );
				}
			}
		}

		protected internal override void End( ShadingContext context, GeometrySource.IPrerenderHandler prerenderHandler )
		{
			context.GraphicsDevice.Indices = null;
		}

		private sealed class PrerenderHandler : IPrerenderHandler
		{
			private SurfaceBuffer owner;
			private DynamicIndexBuffer ib;
			private int offset;

			public PrerenderHandler( SurfaceBuffer owner )
			{
				this.owner = owner;
				ib = new DynamicIndexBuffer( owner.ib.GraphicsDevice,
					owner.ib.SizeInBytes * Material.NumLayers,
					BufferUsage.WriteOnly, owner.ib.IndexElementSize );
			}

			public IndexBuffer IndexBuffer { get { return ib; } }

			#region IPrerenderHandler Members

			public void BeginPrerender( GeometrySource source )
			{
				
			}

			public void Prerender( GeometrySource source, DrawRecord[] surfaces, int index, int count, ShadingContext context )
			{
				int lastSurf = index + count;

				int primCount = 0;
				for( int i = index; i < lastSurf; i++ )
				{
					Surface surf = surfaces[i].surface as Surface;
					primCount += surf.PrimitiveCount;
				}

				int vertSize = owner.sib != null ? 2 : 4;

				if( offset + primCount * 3 * vertSize > ib.SizeInBytes )
					offset = 0;

				surfaces[index].PrerenderParam = offset / vertSize;

				for( int e, s = index; s < lastSurf; s = e )
				{
					Surface ss = surfaces[s].surface as Surface;

					primCount = ss.PrimitiveCount;
					int vertCount = ss.VertexCount;

					for( e = s + 1; e < lastSurf; e++ )
					{
						Surface se = surfaces[e].surface as Surface;
						if( ss.FirstVertex + vertCount != se.FirstVertex )
							break;

						vertCount += se.VertexCount;
						primCount += se.PrimitiveCount;
					}

#if XBOX
					SetDataOptions opts = SetDataOptions.NoOverwrite;
#else
					SetDataOptions opts = offset == 0 ? SetDataOptions.Discard : SetDataOptions.NoOverwrite;
#endif

					int numIndices = primCount * 3;
					if( owner.sib != null )
					{
						ib.SetData( offset, owner.sib, ss.FirstIndex, numIndices, opts );
						offset += numIndices * 2;
					}
					else
					{
						ib.SetData( offset, owner.iib, ss.FirstIndex, numIndices, opts );
						offset += numIndices * 4;
					}
				}					
			}

			public void EndPreRender( GeometrySource source )
			{
				
			}

			#endregion
		}

		protected internal override IPrerenderHandler CreatePrerenderHandler()
		{
#if true
			//This is a massive non-optimization. /sigh wasted time :(
			return null;
#else
			if( sib == null && iib == null )
			{
				if( ib.IndexElementSize == IndexElementSize.SixteenBits )
				{
					sib = new ushort[ib.SizeInBytes / 2];
					ib.GetData( sib );
				}
				else
				{
					iib = new uint[ib.SizeInBytes / 4];
					ib.GetData( iib );
				}
			}

			return new PrerenderHandler( this );
#endif
		}
	}

	[Flags]
	public enum BrushContents
	{
		None = 0,
		Solid = 1,
		Lava = 8,
		Slime = 16,
		Water = 32,
		Fog = 64,

		PlayerClip = 0x10000,
	}

	public sealed class BrushSide
	{
		internal BrushSide() { }

		internal Plane plane;
		public Plane Plane
		{
			get { return plane; }
			internal set { plane = value; }
		}

		private int surfaceFlags;
		public int SurfaceFlags
		{
			get { return surfaceFlags; }
			internal set { surfaceFlags = value; }
		}
	}

	public class Brush
	{
		internal Brush() { }

		private BrushSide[] sides;
		public BrushSide[] Sides
		{
			get { return sides; }
			internal set { sides = value; }
		}

		private BrushContents contentFlags;
		public BrushContents ContentFlags
		{
			get { return contentFlags; }
			internal set { contentFlags = value; }
		}

		public bool Contains( Vector3 point )
		{
			foreach( BrushSide side in sides )
			{
				float sd;
				side.Plane.DotCoordinate( ref point, out sd );

				if( sd > 0 )
					return false;
			}

			return true;
		}

		internal int brushIndex;
	}

	public abstract class Node
	{
		internal Node() { }

		internal BoundingBox bounds;
		public BoundingBox BoundingBox
		{
			get { return bounds; }
		}

		internal int pvsIndex;

		internal Node parent;
		public Node Parent
		{
			get { return parent; }
			internal set { parent = value; }
		}

		internal Surface[] surfaces;
		public Surface[] Surfaces
		{
			get { return surfaces; }
		}
	}

	public sealed class PartitionNode : Node
	{
		internal PartitionNode() { }

		internal Node child0, child1;

		public Node Child0 { get { return child0; } }
		public Node Child1 { get { return child1; } }

		internal Plane plane;
		public Plane Plane
		{
			get { return plane; }
		}
	}

	public sealed class LeafNode : Node
	{
		internal LeafNode() { }

		internal int pvsCluster;

		private Brush[] brushes;
		public Brush[] Brushes
		{
			get { return brushes; }
			internal set { brushes = value; }
		}

		public Brush BrushFromPoint( Vector3 point )
		{
			foreach( Brush brush in brushes )
				if( brush.Contains( point ) )
					return brush;

			return null;
		}
	}

	public sealed class PvsData
	{
		internal PvsData() { }

		private int numClusters;
		public int NumClusters
		{
			get { return numClusters; }
			internal set { numClusters = value; }
		}

		private uint[] pvsData;
		public uint[] PvsBytes
		{
			get { return pvsData; }
			internal set { pvsData = value; }
		}
	}

	public class Entity
	{
		internal Entity() { }

		private float angle;
		private Vector3 origin;
		private string className;
		private Dictionary<string, string> miscValues;

		private Entity target;

		internal virtual void Read( ContentReader reader )
		{
			className = reader.ReadString();
			origin = reader.ReadVector3();
			angle = reader.ReadSingle();
			miscValues = reader.ReadRawObject<Dictionary<string, string>>();
		}

		public Dictionary<string, string> MiscValues
		{
			get { return miscValues; }
		}

		public string ClassName
		{
			get { return className; }
		}

		public Vector3 Origin
		{
			get { return origin; }
		}

		/// <summary>
		/// The entity's angle in radians.
		/// </summary>
		public float Angle
		{
			get { return angle; }
		}

		public Entity Target
		{
			get { return target; }
			internal set { target = value; }
		}
	}

	public class LightEntity : Entity
	{
		internal LightEntity() { }

		private Color color;
		private float intensity;

		internal override void Read( ContentReader reader )
		{
			base.Read( reader );
			color.PackedValue = reader.ReadUInt32();
			intensity = reader.ReadSingle();
		}

		public Color Color
		{
			get { return color; }
		}

		public float Intensity
		{
			get { return intensity; }
		}
	}

	public class WorldSpawnEntity : Entity
	{
		internal WorldSpawnEntity() { }

		private string message;
		private bool deluxeMapping;

		internal override void Read( ContentReader reader )
		{
			base.Read( reader );
			message = reader.ReadString();
			deluxeMapping = reader.ReadBoolean();
		}

		public string Message
		{
			get { return message; }
		}

		public bool DeluxeMapping
		{
			get { return deluxeMapping; }
		}
	}

	public sealed class EntityData
	{
		internal EntityData() { }

		private Entity[] allEntities;
		public Entity[] All
		{
			get { return allEntities; }
			internal set { allEntities = value; }
		}

		private LightEntity[] lights;
		public LightEntity[] Lights
		{
			get { return lights; }
			internal set { lights = value; }
		}
	}

	public sealed partial class BspData : ISurfaceList
	{
		internal BspData()
		{
		}

		#region Data Properties
		private GraphicsDevice graphicsDevice;
		public GraphicsDevice GraphicsDevice
		{
			get { return graphicsDevice; }
			internal set { graphicsDevice = value; }
		}

		private EntityData entities = new EntityData();
		public EntityData Entities
		{
			get { return entities; }
		}

		private Texture2D[] lightmaps;
		public Texture2D[] Lightmaps
		{
			get { return lightmaps; }
			internal set { lightmaps = value; }
		}

		private Surface[] surfaces;
		public Surface[] Surfaces
		{
			get { return surfaces; }
			internal set { surfaces = value; }
		}

		private Brush[] brushes;
		public Brush[] Brushes
		{
			get { return brushes; }
			internal set { brushes = value; }
		}

		private PartitionNode[] partitions;
		public PartitionNode[] Partitions
		{
			get { return partitions; }
			internal set { partitions = value; }
		}

		private LeafNode[] leaves;
		public LeafNode[] Leaves
		{
			get { return leaves; }
			internal set { leaves = value; }
		}

		private PvsData pvs;
		public PvsData PvsData
		{
			get { return pvs; }
			internal set { pvs = value; }
		}
		#endregion

		public LeafNode PointToLeaf( Vector3 point )
		{
			Node node = partitions[0];
			for( ; ; )
			{
				LeafNode leaf = node as LeafNode;
				if( leaf != null )
					return leaf;

				if( node == null )
					return null;

				PartitionNode part = node as PartitionNode;

				float d;
				part.plane.DotCoordinate( ref point, out d );
				node = d > 0 ? part.child0 : part.child1;
			}
		}

		public BrushContents? GetPointContents( Vector3 point )
		{
			LeafNode leaf = PointToLeaf( point );
			if( leaf != null )
			{
				Brush brush = leaf.BrushFromPoint( point );
				if( brush != null )
					return brush.ContentFlags;
			}

			return null;
		}

		private void AppendUnculledSurfaces( DrawList surfList,
			int matrixId, ShadingContext context )
		{
			foreach( Surface surf in surfaces )
			{
				surfList.AddDrawSurface( surf, context.OverrideMaterial ?? surf.Material, matrixId );
			}
		}

		public void AppendSurfaces( DrawList surfList, Pvs pvs, Matrix modelMatrix,
			ShadingContext context)
		{
			if( context == null )
				throw new ArgumentNullException( "context" );
			if( pvs == null )
				throw new ArgumentNullException( "pvs" );
			if( pvs.Owner != this )
				throw new ArgumentException();

			if( partitions.Length > 0 )
				AppendCulledSurfaces( surfList, ref modelMatrix, context, pvs );
			else
				AppendUnculledSurfaces( surfList, surfList.RegisterMatrix( ref modelMatrix ), context );
		}

		public void AppendSurfaces( DrawList surfList, Matrix modelMatrix, ShadingContext context )
		{
			if( context == null )
				throw new ArgumentNullException( "context" );

			AppendUnculledSurfaces( surfList, surfList.RegisterMatrix( ref modelMatrix ), context );
		}

		public void AppendSurfaces( DrawList surfList, int modelMatrixId, ShadingContext context )
		{
			if( context == null )
				throw new ArgumentNullException( "context" );

			AppendUnculledSurfaces( surfList, modelMatrixId, context );
		}

		void ISurfaceList.AppendSurfaces( DrawList surfList, IPvs pvs, Matrix matrix, ShadingContext context )
		{
			if( surfList == null )
				throw new ArgumentNullException( "surfList" );
			if( context == null )
				throw new ArgumentNullException( "context" );
			
			if( partitions.Length > 0 )
			{
				Pvs bspPvs = pvs as Pvs;
				if( bspPvs != null )
				{
					if( bspPvs.Owner != this )
						throw new ArgumentException();

					AppendCulledSurfaces( surfList, ref matrix, context, bspPvs );
					return;
				}
			}

			AppendUnculledSurfaces( surfList, surfList.RegisterMatrix( ref matrix ), context );
		}

		internal void Loaded()
		{
			for( int i = 0; i < leaves.Length; i++ )
				leaves[i].pvsIndex = i;
			for( int i = 0; i < partitions.Length; i++ )
				partitions[i].pvsIndex = leaves.Length + i;

			for( int i = 0; i < brushes.Length; i++ )
				brushes[i].brushIndex = i;
		}

	}

}